Welcome to part two of my series on the development of Haskell bindings for the capstone disassembly framework. Today I will mainly focus on some technical details as well as the decisions made regarding the questions that arose in part 1. But first, a small recap of the work already done.
So far, I have started the project and wrote a good deal of code to do the actual work needed. This involved setting up stack
in a proper fashion, which turned out to be a breeze, as it runs preprocessors as needed out of the box. This especially means that using c2hs
, which after short inspection revealed itself as the most suited tool for the job is pretty easy and well-integrated as well.
So I started porting the headers that capstone puts into /usr/include
on my arch installation, and which are available on their github presence. This is fairly easy, but at some point I started to hit weird errors in GHCI
regarding compilation to bytecode. Some further analysis showed that this was due to the usage of user-defined in-marshallers in fun
-hooks. Hm. I don't really know what is causing this issue, and so far there are only two candidates: c2hs
and GHCI
itself. This needs a more through look.
Most of the headers are pretty straightforward: porting enumerations is pretty easy if you can reduce the typing necessary to one line:
{#enum arm64_shifter as Arm64Shifter {underscoreToCase} deriving (Show)#}
And most of the architecture-specific headers consist to 90-95% of such enumerations. Much more intriguing is the marshalling of structures, as c2hs
provides no mechanism to automate that task. Moreover, anonymous unions don't seem to be supported at all, so I had to work around that somehow. To make this post less boring, I'll include my first (and so far only) attempt to marshal such a complex structure from C to Haskell. It involves some offset-oriented hacks that might break, but I need some digging into disassembly and/or relevant standard specs to say anything specific.
And here, in all it's glory, a snippet that represents an instruction operand of the ARM64 aka ARMv8 architecture (boring parts omitted, code taken from arm64.h
):
// Instruction operand
typedef struct cs_arm64_op {
int vector_index; // Vector Index for some vector operands (or -1 if irrelevant)
arm64_vas vas; // Vector Arrangement Specifier
arm64_vess vess; // Vector Element Size Specifier
struct {
arm64_shifter type; // shifter type of this operand
unsigned int value; // shifter value of this operand
} shift;
arm64_extender ext; // extender type of this operand
arm64_op_type type; // operand type
union {
unsigned int reg; // register value for REG operand
int64_t imm; // immediate value, or index for C-IMM or IMM operand
double fp; // floating point value for FP operand
arm64_op_mem mem; // base/index/scale/disp value for MEM operand
arm64_pstate pstate; // PState field of MSR instruction.
unsigned int sys; // IC/DC/AT/TLBI operation (see arm64_ic_op, arm64_dc_op, arm64_at_op, arm64_tlbi_op)
arm64_prefetch_op prefetch; // PRFM operation.
arm64_barrier_op barrier; // Memory barrier operation (ISB/DMB/DSB instructions).
};
} cs_arm64_op;
and the corresponding Haskell datatype and it's Storable
instance
instance Storable CsArm64Op where
sizeOf _ = {#sizeof cs_arm64_op#}
alignment _ = {#alignof cs_arm64_op#}
peek p = CsArm64Op
<$> (fromIntegral <$> {#get cs_arm64_op->vector_index#} p)
<*> ((toEnum . fromIntegral) <$> {#get cs_arm64_op->vas#} p)
<*> ((toEnum . fromIntegral) <$> {#get cs_arm64_op->vess#} p)
<*> ((,) <$>
((toEnum . fromIntegral) <$> {#get cs_arm64_op->shift.type#} p) <*>
(fromIntegral <$> {#get cs_arm64_op->shift.value#} p))
<*> ((toEnum . fromIntegral) <$> {#get cs_arm64_op->ext#} p)
<*> do
t <- fromIntegral <$> {#get cs_arm64_op->type#} p :: IO Int
let bP = plusPtr p -- FIXME: maybe alignment will bite us!
({#offsetof cs_arm64_op.type#} + {#sizeof arm64_op_type#})
case toEnum t :: Arm64OpType of
Arm64OpReg -> (Reg . fromIntegral) <$> (peek bP :: IO CUInt)
Arm64OpImm -> (Imm . fromIntegral) <$> (peek bP :: IO Int64)
Arm64OpCimm -> (CImm . fromIntegral) <$> (peek bP :: IO Int64)
Arm64OpFp -> (Fp . realToFrac) <$> (peek bP :: IO CDouble)
Arm64OpMem -> Mem <$> (peek bP :: IO Arm64OpMemStruct)
Arm64OpRegMsr -> (Pstate . toEnum . fromIntegral) <$>
(peek bP :: IO CInt)
Arm64OpSys -> (Sys . fromIntegral) <$> (peek bP :: IO CUInt)
Arm64OpPrefetch -> (Prefetch . toEnum . fromIntegral) <$>
(peek bP :: IO CInt)
Arm64OpBarrier -> (Barrier . toEnum . fromIntegral) <$>
(peek bP :: IO CInt)
_ -> return Undefined
poke p (CsArm64Op vI va ve (sh, shV) ext val) = do
{#set cs_arm64_op->vector_index#} p (fromIntegral vI)
{#set cs_arm64_op->vas#} p (fromIntegral $ fromEnum va)
{#set cs_arm64_op->vess#} p (fromIntegral $ fromEnum ve)
{#set cs_arm64_op->shift.type#} p (fromIntegral $ fromEnum sh)
{#set cs_arm64_op->shift.value#} p (fromIntegral shV)
{#set cs_arm64_op->ext#} p (fromIntegral $ fromEnum ext)
let bP = plusPtr p -- FIXME: maybe alignment will bite us!
({#offsetof cs_arm64_op.type#} + {#sizeof arm64_op_type#})
setType = {#set cs_arm64_op->type#} p . fromIntegral . fromEnum
case val of
Reg r -> do
poke bP (fromIntegral r :: CUInt)
setType Arm64OpReg
Imm i -> do
poke bP (fromIntegral i :: Int64)
setType Arm64OpImm
CImm i -> do
poke bP (fromIntegral i :: Int64)
setType Arm64OpCimm
Fp f -> do
poke bP (realToFrac f :: CDouble)
setType Arm64OpFp
Mem m -> do
poke bP m
setType Arm64OpMem
Pstate p -> do
poke bP (fromIntegral $ fromEnum p :: CInt)
setType Arm64OpRegMsr
Sys s -> do
poke bP (fromIntegral s :: CUInt)
setType Arm64OpSys
Prefetch p -> do
poke bP (fromIntegral $ fromEnum p :: CInt)
setType Arm64OpPrefetch
Barrier b -> do
poke bP (fromIntegral $ fromEnum b :: CInt)
setType Arm64OpBarrier
_ -> setType Arm64OpInvalid
Most of the code is self-explanatory after a lecture of the c2hs
docs and the basic idea behind the Storable
typeclass. Some interesting bits are the large case-expression near the end. This is my take on porting C-style tagged unions to sum datatypes in Haskell. This could be much more pleasant if anonymous unions were supported by c2hs
, but instead I had to rely on ugly pointer juggling to get to the memory location of interest. This might need fixing. Most of the other structures should be similar (I haven't done them yet). Another bit worth showing off before I get the code on GitHub is the heart of capstone's API: cs_disasm
. Because of the issues with GHCI
mentioned earlier, I resorted to writing the marshalling by hand (see capstone.h
for the C source):
size_t cs_disasm(csh handle,
const uint8_t *code, size_t code_size,
uint64_t address,
size_t count,
cs_insn **insn);
The corresponding Haskell code:
foreign import ccall "capstone/capstone.h cs_disasm"
csDisasm' :: Csh -- handle
-> Ptr CUChar -> CSize -- buffer to disassemble
-> CULong -- address to start at
-> CSize -- number of instructins to disassemble
-> Ptr (Ptr CsInsn) -- where to put the instructions
-> IO CSize -- number of succesfully disassembled instructions
csDisasm :: Csh -> [Word8] -> Word64 -> Int -> IO [CsInsn]
csDisasm handle bytes addr num = do
array <- newArray $ map fromIntegral bytes
passedPtr <- malloc :: IO (Ptr (Ptr CsInsn))
resNum <- fromIntegral <$> csDisasm' handle array
(fromIntegral $ length bytes) (fromIntegral addr)
(fromIntegral num) passedPtr
resPtr <- peek passedPtr
free passedPtr
res <- mapM (peek . plusPtr resPtr . ({#sizeof cs_insn#} *)) [0..resNum]
csFree resPtr resNum
return res
The code should be pretty simple to understand as well, but I thought it is equally fun to read as it was to write (especially the memory handling).
Another bit worth mentioning is the handling of overflows in the marshalling code. In general, we have two choices: either cut off the superfluous data, or raise errors. I've resorted to the latter, but I might have to rethink that decision one day.
That's it for now, I'll add subsequent articles when some progress has been made.
back home